package models.games;

import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;

import models.util.BalloonThread;
import core.User;
import core.constants.Constants;

public class PallonciniMatematici extends Game {

	private int remainingErrors = Constants.remainingErrors();
	private ConcurrentHashMap<Integer, LinkedList<BalloonThread>> balloons = new ConcurrentHashMap<Integer, LinkedList<BalloonThread>>();
	private final static int MAX_COMPRESENT = 6;// Palloncini che possono essere
	// contemporaneamente presenti
	private BalloonThread.Velocity currentVelocity = BalloonThread.Velocity.LOW;
	private Semaphore generateBalloon = new Semaphore(MAX_COMPRESENT);
	private int[] slots = new int[MAX_COMPRESENT];
	private Semaphore mutex = new Semaphore(1);

	public PallonciniMatematici(User user) {
		super(user);
	}

	public boolean gameIsFinished() {
		return remainingErrors == 0;
	}

	public int getRemainingErrors() {
		return remainingErrors;
	}

	/**
	 * crea e avvia un nuovo BalloonThread e lo inserisce nella mappa "balloons"
	 * in base al suo risultato
	 */
	public BalloonThread generateBalloon() {
		try {
			generateBalloon.acquire();
			mutex.acquire();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		int slot = chooseSlot();
		mutex.release();
		BalloonThread b = new BalloonThread(currentVelocity, this, slot);
		int result = b.getResult();
		if (!balloons.containsKey(result)) {
			LinkedList<BalloonThread> list = new LinkedList<BalloonThread>();
			list.add(b);
			balloons.put(result, list);
		} else {
			LinkedList<BalloonThread> list = balloons.get(result);
			list.add(b);
		}
		b.start();
		System.out.println("Palloncino : " + b);
		return b;
	}

	private int chooseSlot() {
		for (int i = 0; i < slots.length; i++)
			if (slots[i] == 0) {
				slots[i] = 1;
				return i;
			}
		return -1;
	}

	public void releaseSlot(int i) {
		try {
			mutex.acquire();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		slots[i] = 0;
		mutex.release();
		generateBalloon.release();
	}

	/**
	 * Esplode tutti i palloncini contenuti nella lista passata come parametro (
	 * terminando i thread ) e SENZA rimuoverli dalla mappa
	 * 
	 * @param balloonsList
	 * @return il numero di palloncini esplosi
	 */
	private int explodeList(List<BalloonThread> balloonsList) {
		int quantity = 0;
		synchronized (balloonsList) {
			for (BalloonThread b : balloonsList) {
				b.interrupt();
				quantity++;
			}
		}
		return quantity;
	}

	/**
	 * Esplode tutti i palloncini ( terminando i thread ) e li rimuove dalla
	 * mappa. Metodo Synchronized per evitare concurrentModificationException
	 * sulla lista di keys.
	 * 
	 * @return il numero di palloncini esplosi
	 * @throws InterruptedException
	 */
	public int explodeAll() throws InterruptedException {
		Set<Integer> keys = balloons.keySet();
		int quantity = 0;
		synchronized (keys) {
			for (int key : keys) {
				List<BalloonThread> balloonsList = balloons.get(key);
				quantity += explodeList(balloonsList);
			}
			balloons.clear();
			remainingErrors--;
		}

		return quantity;
	}

	/**
	 * @param result
	 * @return true se il risultato è associato a qualche palloncino ( che viene
	 *         esploso); false altrimenti
	 */
	public boolean verify(int result) {
		boolean right = balloons.containsKey(result);
		if (!right)
			return right;
		List<BalloonThread> balloonsList = balloons.get(result);
		score += explodeList(balloonsList) * 250;
		if (score % 1000 == 0 && getLevel() < 5) {
			setLevel(getLevel() + 1);
			int l = getLevel();
			switch (l) {
			case 0:
				currentVelocity = BalloonThread.Velocity.LOW;
				break;
			case 1:
				currentVelocity = BalloonThread.Velocity.MEDIUM_LOW;
				break;
			case 2:
				currentVelocity = BalloonThread.Velocity.MEDIUM;
				break;
			case 3:
				currentVelocity = BalloonThread.Velocity.MEDIUM_HIGH;
				break;
			case 4:
				currentVelocity = BalloonThread.Velocity.HIGH;
				break;
			}
		}
		return right;
	}

	@Override
	public int getGameKey() {
		return 10;
	}

}
